/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.internal.core.handler;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JCast;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.ElementValidation;
import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.annotations.ServiceAction;
import org.ohosannotations.handler.BaseAnnotationHandler;
import org.ohosannotations.helper.CaseHelper;
import org.ohosannotations.holder.EIntentServiceHolder;

import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr.lit;
import static com.helger.jcodemodel.JMod.FINAL;
import static com.helger.jcodemodel.JMod.PUBLIC;
import static com.helger.jcodemodel.JMod.STATIC;

/**
 * 服务操作处理程序
 *
 * @author dev
 * @since 2021-07-22
 */
public class ServiceActionHandler extends BaseAnnotationHandler<EIntentServiceHolder> {
    /**
     * 服务操作处理程序
     *
     * @param environment 环境
     */
    public ServiceActionHandler(OhosAnnotationsEnvironment environment) {
        super(ServiceAction.class, environment);
    }

    @Override
    protected void validate(Element element, ElementValidation validation) {
        validatorHelper.enclosingElementHasEIntentService(element, validation);

        validatorHelper.returnTypeIsVoid((ExecutableElement) element, validation);

        validatorHelper.isNotPrivate(element, validation);
    }

    @Override
    public void process(Element element, EIntentServiceHolder holder) throws Exception {
        ExecutableElement executableElement = (ExecutableElement) element;
        String methodName = element.getSimpleName().toString();

        ServiceAction annotation = element.getAnnotation(ServiceAction.class);
        String extraKey = annotation.value();
        if (extraKey.isEmpty()) {
            extraKey = methodName;
        }

        JFieldVar actionKeyField = createStaticActionField(holder, extraKey, methodName);
        addActionInOnHandleIntent(holder, executableElement, methodName, actionKeyField);
        addActionToIntentBuilder(holder, executableElement, methodName, actionKeyField);
    }

    private JFieldVar createStaticActionField(EIntentServiceHolder holder, String extraKey, String methodName) {
        String staticFieldName = CaseHelper.camelCaseToUpperSnakeCase("action", methodName, null);
        return holder.getGeneratedClass()
            .field(PUBLIC | STATIC | FINAL, getClasses().STRING, staticFieldName, lit(extraKey));
    }

    private void addActionInOnHandleIntent(EIntentServiceHolder holder,
        ExecutableElement executableElement,
        String methodName, JFieldVar actionKeyField) {
        JInvocation actionCondition = actionKeyField.invoke("equals").arg(holder.getOnHandleIntentIntentAction());
        JBlock callActionBlock = holder.getOnHandleIntentBody()._if(actionCondition)._then();
        JInvocation callActionInvocation = JExpr._super().invoke(methodName);

        // For each method params, we get back value from extras and put it
        // in super calls
        List<? extends VariableElement> methodParameters = executableElement.getParameters();
        if (methodParameters.size() > 0) {
            // IntentParams
            JVar intent = holder.getOnHandleIntentIntent();
            JVar params = callActionBlock.decl(getClasses().INTENT_PARAMS, "params");
            params.init(intent.invoke("getParams"));
            callActionBlock = callActionBlock._if(params.ne(_null()))._then();

            // params
            for (VariableElement param : methodParameters) {
                String paramName = param.getSimpleName().toString();
                String extraParamName = paramName + "Param";
                AbstractJClass extraParamClass = codeModelHelper.typeMirrorToJClass(param.asType());
                IJExpression getExtraExpression = JExpr.invoke(params, "getParam").arg(paramName);
                JCast jCast = JExpr.cast(extraParamClass, getExtraExpression);
                JVar extraField = callActionBlock.decl(extraParamClass, extraParamName, jCast);
                callActionInvocation.arg(extraField);
            }
        }
        callActionBlock.add(callActionInvocation);
        callActionBlock._return();
    }

    private void addActionToIntentBuilder(EIntentServiceHolder holder,
        ExecutableElement executableElement,
        String methodName, JFieldVar actionKeyField) {
        JMethod method = holder.getIntentBuilderClass().method(PUBLIC, holder.getIntentBuilderClass(), methodName);
        JBlock body = method.body();

        String docComment = getProcessingEnvironment().getElementUtils().getDocComment(executableElement);
        codeModelHelper.addTrimmedDocComment(method, docComment);
        method.javadoc().addReturn().append("the IntentBuilder to chain calls");

        // setAction
        body.invoke("action").arg(actionKeyField);

        for (VariableElement param : executableElement.getParameters()) {
            String paramName = param.getSimpleName().toString();
            AbstractJClass parameterClass = codeModelHelper.typeMirrorToJClass(param.asType());

            JFieldVar paramVar = getStaticExtraField(holder, paramName);
            JVar methodParam = method.param(parameterClass, paramName);

            JInvocation putExtraInvocation = holder.getIntentBuilder()
                .getSuperPutExtraInvocation(param.asType(), methodParam, paramVar);
            body.add(putExtraInvocation);
        }
        body._return(JExpr._this());
    }

    private JFieldVar getStaticExtraField(EIntentServiceHolder holder, String extraName) {
        String staticFieldName = CaseHelper.camelCaseToUpperSnakeCase(null, extraName, "Extra");
        JFieldVar staticExtraField = holder.getGeneratedClass().fields().get(staticFieldName);
        if (staticExtraField == null) {
            staticExtraField = holder.getGeneratedClass()
                .field(PUBLIC | STATIC | FINAL, getClasses().STRING, staticFieldName, lit(extraName));
        }
        return staticExtraField;
    }
}
